#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>

#define MAXEVENTS 100

// 把socket设置为非阻塞的方式。
int setnonblocking(int sockfd);

// 初始化服务端的监听端口。
int initserver(int port);

int main(int argc,char *argv[])
{
  if (argc != 2)
  {
    printf("usage:./tcpepoll port\n"); return -1;
  }

  // 初始化服务端用于监听的socket。
  int listensock = initserver(atoi(argv[1]));
  printf("listensock=%d\n",listensock);

  if (listensock < 0)
  {
    printf("initserver() failed.\n"); return -1;
  }

  int epollfd;

  char buffer[1024];
  memset(buffer,0,sizeof(buffer));

  //
  /**
  epoll即eventpoll
  epoll_create创建一个eventpoll对象ep，并返回一个描述符表示该对象，
  // file：fs/eventpoll.c
  struct eventpoll {
      //sys_epoll_wait用到的等待队列
      wait_queue_head_t wq;
      //接收就绪的描述符都会放到这里
      struct list_head rdllist;
      //每个epoll对象中都有一颗红黑树，用于存放要监视的socketfd
      struct rb_root rbr;
      ......
  }
  其中wq为等待队列链表，软中断数据就绪的时候会通过 wq 来找到阻塞在 epoll 对象上的用户进程。（epoll_wait若就绪队列中无数据，
  会将当前进程加入到等待队列中）。
  rdllist标识就绪的文件描述符链表，当有的连接就绪的时候，内核会把就绪的连接放到 rdllist 链表里。这样应用进程只需要判断链表就
  能找出就绪进程，而不用去遍历整棵树。
  rbr为一颗红黑树，采用红黑树的结构为epoll高效的处理海量数据的增删改查，这个红黑树用于管理用户进程添加进来的所有socket连接。
  */
  epollfd = epoll_create(1);

  // 添加监听描述符事件
  struct epoll_event ev;
  ev.data.fd = listensock;
  ev.events = EPOLLIN;
  /**
  man 2中对该函数的解释：
  #include <sys/epoll.h>
  函数原型：int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  epfd：是一个fd，其作用是引用通过epoll_create(1);创建的epoll对象；
  op：操作，有效取值为：
        EPOLL_CTL_ADD：将fd注册到epfd引用的epoll对象上，并将event与fd指向的内部文件关联起来；
        EPOLL_CTL_MOD：修改fd关联的event；
        EPOLL_CTL_DEL：从epfd引用的epoll对象上删除fd，event参数可忽略；
  event：epoll_event类型，关联到fd，用于描述fd，epoll_event类型定义如下：
           typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      // Epoll events
               epoll_data_t data;        // User data variable
           };
        events：Epoll events，是一个bitmap，表示事件类型，用户关心的事件类型，多个类型可以通过"或"操作表示，有效取值：
            EPOLLIN：fd可读；
            EPOLLOUT：fd可写；
            EPOLLRDHUP：对端socket关闭连接或建立tcp连接握手时停止了；
            EPOLLPRI：fd异常；
            EPOLLERR：fd发生错误，不必设置；
            EPOLLHUP：fd挂起，不必设置；

  */
  epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev);

  while (1)
  {
    struct epoll_event events[MAXEVENTS]; // 存放有事件发生的结构数组。

    // 等待监视的socket有事件发生。
    /**
    函数原型：int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    等待注册到epollfd下fd有事件发生，只有有一个fd发生了感兴趣的事件，该函数就返回，一次最多返回MAXEVENTS个，
    MAXEVENTS必须大于0，发生的事件会被放在用户空间数组events[MAXEVENTS]中。
    当且仅当以下条件发生时，epoll_wait函数返回：
        -- 至少有一个fd发生了事件
        -- 函数被信号中断
        -- 超时时间timeout到
    timeout为-1表示一直阻塞，为0表示立即返回。
    */
    int infds = epoll_wait(epollfd,events,MAXEVENTS,-1);
    // printf("epoll_wait infds=%d\n",infds);

    // 返回失败。
    if (infds < 0)
    {
      printf("epoll_wait() failed.\n"); perror("epoll_wait()"); break;
    }

    // 超时。
    if (infds == 0)
    {
      printf("epoll_wait() timeout.\n"); continue;
    }

    // 遍历有事件发生的结构数组。
    for (int ii=0;ii<infds;ii++)
    {
      if ((events[ii].data.fd == listensock) &&(events[ii].events & EPOLLIN))
      {
        // 如果发生事件的是listensock，表示有新的客户端连上来。
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
        if (clientsock < 0)
        {
          printf("accept() failed.\n"); continue;
        }

        // 把新的客户端添加到epoll中。
        memset(&ev,0,sizeof(struct epoll_event));
        ev.data.fd = clientsock;
        ev.events = EPOLLIN;
        epoll_ctl(epollfd,EPOLL_CTL_ADD,clientsock,&ev);

        printf ("client(socket=%d) connected ok.\n",clientsock);

        continue;
      }
      else if (events[ii].events & EPOLLIN)
      {
        // 客户端有数据过来或客户端的socket连接被断开。
        char buffer[1024];
        memset(buffer,0,sizeof(buffer));

        // 读取客户端的数据。
        ssize_t isize=read(events[ii].data.fd,buffer,sizeof(buffer));

        // 发生了错误或socket被对方关闭。
        if (isize <=0)
        {
          printf("client(eventfd=%d) disconnected.\n",events[ii].data.fd);

          // 把已断开的客户端从epoll中删除。
          memset(&ev,0,sizeof(struct epoll_event));
          ev.events = EPOLLIN;
          ev.data.fd = events[ii].data.fd;
          epoll_ctl(epollfd,EPOLL_CTL_DEL,events[ii].data.fd,&ev);
          close(events[ii].data.fd);
          continue;
        }

        printf("recv(eventfd=%d,size=%d):%s\n",events[ii].data.fd,isize,buffer);

        // 把收到的报文发回给客户端。
        write(events[ii].data.fd,buffer,strlen(buffer));
      }
    }
  }

  close(epollfd);

  return 0;
}

// 创建一个socket，设置属性，绑定端口，启动监听。
int initserver(int port)
{
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if (sock < 0)
  {
    printf("socket() failed.\n"); return -1;
  }

  // Linux如下
  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
  setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);

  struct sockaddr_in servaddr;
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(port);

  if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
  {
    printf("bind() failed.\n"); close(sock); return -1;
  }

  if (listen(sock,5) != 0 )
  {
    printf("listen() failed.\n"); close(sock); return -1;
  }

  return sock;
}

// 把socket设置为非阻塞的方式。
int setnonblocking(int sockfd)
{
  if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)  return -1;

  return 0;
}
